home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 January: Mac OS SDK / Dev.CD Jan 97 SDK1.toast / Development Kits (Disc 1) / QuickDraw GX / Programming Stuff / GX Libraries / ShapeControlsLibrary.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-03-31  |  44.4 KB  |  1,344 lines  |  [TEXT/MPS ]

  1.  
  2. /*
  3.     File:        ShapeControlsLibrary.c
  4.  
  5.     Contains:    graphics libraries - library to allow the user to manipulate (gxTransform) a set of shapes
  6.     
  7.     Written by:    Dave Good and Jeff Kreegar
  8.     
  9.     Copyright:    © 1995 by Apple Computer, Inc., all rights reserved.
  10.  
  11.     Change History (most recent first):
  12.  
  13.          <2>      1/9/95    JD        changed 'boolean' to 'Boolean'
  14.          <1>      1/9/95    JD        First checked in.
  15. */
  16.  
  17. #include <Memory.h>
  18. #include <Events.h>
  19. #include <OSUtils.h>
  20. #include <ToolUtils.h>
  21. #include <GXErrors.h>
  22. #include <GXEnvironment.h>
  23. #include "GraphicsLibraries.h"
  24. #include "ShapeControlsLibrary.h"
  25.  
  26.  
  27. /*----------------------------------------------------------------------------------------------------*/
  28. /* private constants and structures */
  29. /*----------------------------------------------------------------------------------------------------*/
  30.  
  31.  
  32. /*** this is not used or completed; however it is a placeholder for the future */
  33. typedef struct portBuffer {
  34.     gxViewGroup   group;
  35. } portBuffer;
  36.  
  37.  
  38. typedef struct shapeControlRecord {
  39.     /* ----- can be accessed in a public way ----- */
  40.     gxShape       items;
  41.     gxShape       foreground;
  42.     gxShape       background;
  43.     gxShape       selection;
  44.     /* ----- private fields follow ----- */
  45.     gxViewPort          **originalPorts;
  46.     gxShape       controls;
  47.     gxPoint           savedCenter;              /* this is either (gxPositiveInfinity, gxPositiveInfinity) or the saved rotate center */
  48.     long            buildHiddenLevel;
  49.     gxRectangle       **invalidAreas;
  50. } shapeControlRecord, *shapeControlPtr;
  51.  
  52.  
  53. #define controlHandleGap        ff(6)        /* this is the distance between the bounds of the gxShape and the bounds of the control handles */
  54. #define minimumObjectSizeH        ff(2)        /* this is the smallest horizontal size that an object may be scaled to */
  55. #define minimumObjectSizeV        ff(2)        /* this is the smallest vertical size that an object may be scaled to */
  56. #define rotateControlRadius     ff(7)
  57. #define rotateCenterSize        ff(2)
  58.  
  59.  
  60. typedef enum {
  61.     /* ----- special commands ----- */
  62.     firstSpecialCommand = -1,
  63.     commandSelect = firstSpecialCommand,
  64.     commandMove,
  65.     lastSpecialCommand = commandMove,
  66.     /* ----- indexes into the shapeControl->controls picture that correspond to different commands ----- */
  67.     firstIndexCommand = 1,
  68.     commandRotate = firstIndexCommand,
  69.     commandMoveRotateCenter,
  70.     commandScale,
  71.     commandSkew = commandScale + 4,
  72.     lastIndexCommand = commandSkew + 3,
  73.     /* ----- modifiers for commandScale follow ----- */
  74.     leftTop = 0,
  75.     rightTop,
  76.     rightBottom,
  77.     leftBottom,
  78.     /* ----- modifiers for commandSkew follow ----- */
  79.     centerTop = 0,
  80.     centerRight,
  81.     centerBottom,
  82.     centerLeft
  83. } shapeControlCommands;
  84.  
  85.  
  86. /*----------------------------------------------------------------------------------------------------*/
  87. /* private generic routines */
  88. /*----------------------------------------------------------------------------------------------------*/
  89.  
  90.  
  91. static Fixed FixedAbs(Fixed value)
  92. {
  93.     if( value < 0 )
  94.         return -value;
  95.     return value;
  96. }
  97.  
  98. static gxViewPort *DereferenceViewPortList(gxViewPort **list)
  99. {
  100.     HLock((Handle)list);
  101.     return *list;
  102. }
  103.  
  104. static void ReleaseViewPortList(gxViewPort **list)
  105. {
  106.     HUnlock((Handle)list);
  107. }
  108.  
  109. static gxViewPort **NewViewPortListFromShape(gxShape source)
  110. {
  111.     gxViewPort **list = (gxViewPort **)NewHandleClear((GXGetShapeViewPorts(source, nil) + 1) * sizeof(gxViewPort));
  112.  
  113.     IfErrorReturnNil(list == nil, out_of_memory);
  114.     GXGetShapeViewPorts(source, DereferenceViewPortList(list));
  115.     ReleaseViewPortList(list);
  116.     return list;
  117. }
  118.  
  119. static long CountViewPortList(gxViewPort **list)
  120. {
  121.     return (GetHandleSize((Handle)list) / sizeof(gxViewPort)) - 1;
  122. }
  123.  
  124. static void DisposeViewPortList(gxViewPort **list)
  125. {
  126.     NilParamReturn(list);
  127.     DisposeHandle((Handle)list);
  128. }
  129.  
  130. static Boolean ShapeInPicture(gxShape test, gxShape picture)
  131. {
  132.     long count;
  133.  
  134.     NilShapeReturnNil(test);
  135.     NilShapeReturnNil(picture);
  136.  
  137.     count = GXGetPicture(picture, nil, nil, nil, nil);
  138.     while( count ) {
  139.         gxShape item;
  140.  
  141.         GXGetPictureParts(picture, count, 1, &item, nil, nil, nil);
  142.         if( item == test )
  143.             return true;
  144.         --count;
  145.     }
  146.     return false;
  147. }
  148.  
  149. static void GetPictureBounds(gxShape picture, gxRectangle *bounds, gxMapping *concatMatrix)
  150. {
  151.     long count = GXGetPicture(picture, nil, nil, nil, nil);
  152.  
  153.     /* if there are no items in the picture, then there is nothing to do */
  154.     if( count == 0 )
  155.         return;
  156.  
  157.     do {    gxShape item;
  158.         gxTransform itemXform;
  159.         gxMapping matrix;
  160.  
  161.         GXGetPictureParts(picture, count, 1, &item, nil, nil, &itemXform);
  162.  
  163.         /* if there was no overriding gxTransform, then use the item’s gxTransform. calculate the concatenated gxMapping */
  164.         if( itemXform == nil )
  165.             itemXform = GXGetShapeTransform(item);
  166.         MapMapping(GXGetTransformMapping(itemXform, &matrix), concatMatrix);
  167.  
  168.         if( GXGetShapeType(item) == gxPictureType ) {
  169.             GetPictureBounds(item, bounds, &matrix);
  170.         } else {
  171.             gxRectangle itemBounds;
  172.             gxMapping tempMatrix;
  173.             gxShape boundsShape = GXNewRectangle( GXGetShapeLocalBounds(item, &itemBounds) );
  174.  
  175.             /* map our bounds gxRectangle through the concatenated gxMapping. note that since GXGetShapeLocalBounds already
  176.             maps the bounds by the item’s gxTransform, we need to undo this effect. */
  177.         #ifdef debugging
  178.             GXIgnoreGraphicsNotice(mapping_unaffected);
  179.         #endif
  180.                 MapMapping(&matrix, InvertMapping(&tempMatrix, GXGetTransformMapping(GXGetShapeTransform(item), &tempMatrix)));
  181.                 GXMapShape(boundsShape, &matrix);
  182.         #ifdef debugging
  183.             GXPopGraphicsNotice();
  184.         #endif
  185.             /* get the bounds of our transformed bounds gxRectangle and then dispose of the temporary bounds gxShape */
  186.             GXGetShapeBounds(boundsShape, 0L, &itemBounds);
  187.             GXDisposeShape(boundsShape);
  188.  
  189.             /* union this item’s bounds into the main picture bounds (work around a problem with GXUnionRectangle) */
  190.             if( bounds->right < bounds->left || bounds->bottom < bounds->top )
  191.                 *bounds = itemBounds;
  192.             else
  193.                 GXUnionRectangle(bounds, bounds, &itemBounds);
  194.         }
  195.     } while( --count );
  196. }
  197.  
  198. static gxRectangle *GetShapeTransformedBounds(gxShape source, gxRectangle *bounds)
  199. {
  200.     gxMapping matrix;
  201.  
  202.     /* since graphics currently can’t get the correct bounds for a picture gxShape, we need special code */
  203.     if( GXGetShapeType(source) == gxPictureType ) {
  204.         bounds->left = bounds->top = gxPositiveInfinity;
  205.         bounds->right = bounds->bottom = gxNegativeInfinity;
  206.         GetPictureBounds( source, bounds, GXGetShapeMapping(source, &matrix) );
  207.         return bounds;
  208.     }
  209.  
  210.     /* otherwise, just use GXGetShapeLocalBounds */
  211.     return GXGetShapeLocalBounds(source, bounds);
  212. }
  213.  
  214. static gxPoint *GetShapeTransformedCenter(gxShape source, gxPoint *center)
  215. {
  216.     gxMapping matrix;
  217.  
  218.     /* since graphics currently can’t calculate the center of picture shapes, we need special code */
  219.     if( GXGetShapeType(source) == gxPictureType ) {
  220.         gxRectangle bounds;
  221.  
  222.         GetShapeTransformedBounds(source, &bounds);
  223.         center->x = (bounds.right >> 1) + (bounds.left >> 1);
  224.         center->y = (bounds.top >> 1) + (bounds.bottom >> 1);
  225.     } else
  226.         MapPoints( GXGetShapeMapping(source, &matrix), 1, GXGetShapeCenter(source, 0L, center) );
  227.     return center;
  228. }
  229.  
  230.  
  231. /*----------------------------------------------------------------------------------------------------*/
  232. /* private internal routines */
  233. /*----------------------------------------------------------------------------------------------------*/
  234.  
  235.  
  236. #if 0
  237. static Boolean AllocateOffscreen(shapeControl source)
  238. {
  239.     return false;
  240.     gxBitmap deepestBits;
  241.     gxShape area;
  242.  
  243.     /* this ensures that the first device that we find will be put in deepestBits */
  244.     deepestBits.pixelSize = -1;
  245.  
  246.     area = GXNewShape(gxFullType);
  247.     GXSetShapeViewPorts(area, portCount, ports);
  248.     while( portCount-- ) {
  249.         gxViewDevice devices[maximumViewThings];
  250.         long deviceCount;
  251.  
  252.         if( (deviceCount = GXGetShapeGlobalViewDevices(area, ports[portCount], devices)) > maximumViewThings )
  253.             DebugStr("\pexceded the maximum number of viewDevices for a gxShape control");
  254.         while( deviceCount-- ) {
  255.             gxShape bitsShape;
  256.             gxBitmap bits;
  257.  
  258.             bitsShape = GXGetViewDeviceBitmap(devices[deviceCount]);
  259.             GXGetBitmap(bitsShape, &bits, nil);
  260.             if( bits.pixelSize > deepestBits.pixelSize )
  261.                 deepestBits = bits;
  262.             GXDisposeShape(bitsShape);
  263.         }
  264.     }
  265.     GXDisposeShape(area);
  266.  
  267.     if( deepestBits.pixelSize > 0 ) {
  268.         gxShape bitsShape;
  269.  
  270.         deepestBits.image = nil;    /* make graphics allocate our offscreen bit image */
  271.         bitsShape = GXNewBitmap(&deepestBits, nil);
  272.         CreateOffscreen(&source->buffer, bitsShape);
  273.         GXDisposeShape(bitsShape);
  274.     } else
  275.         FillBytes(&source->buffer, sizeof(source->buffer), 0);
  276.  
  277.     /*** needs work */
  278. }
  279. #endif
  280.  
  281. static void InvalidateRectangle(shapeControlPtr targetPtr, gxRectangle *newArea)
  282. {
  283.     if( targetPtr->invalidAreas ) {
  284.         gxRectangle *listPtr = *targetPtr->invalidAreas;
  285.         gxRectangle *maxPtr = (gxRectangle *)((char *)listPtr + GetHandleSize((Handle)targetPtr->invalidAreas));
  286.  
  287.         while( listPtr != maxPtr &&
  288.             (listPtr->top < newArea->top || listPtr->top == newArea->top && listPtr->left < newArea->left) ) {
  289.             ++listPtr;
  290.         }
  291.         Munger((Handle)targetPtr->invalidAreas, (char *)listPtr - (char *)*targetPtr->invalidAreas, nil, 0,
  292.             (Ptr)newArea, sizeof(gxRectangle));
  293.     } else {
  294.         targetPtr->invalidAreas = (gxRectangle **)NewHandle(sizeof(gxRectangle));
  295.         **targetPtr->invalidAreas = *newArea;
  296.  
  297.         /*** reduce the gxRectangle list here */
  298.     }
  299.  
  300. #if 0  /* re-enable this to show which areas will be updated */
  301.     {    gxShape invalidShape = GXNewRectangle(newArea);
  302.     #ifdef debugging
  303.         GXIgnoreGraphicsNotice(color_already_set);
  304.     #endif
  305.             SetShapeCommonColor(invalidShape, gxBlack);
  306.     #ifdef debugging
  307.         GXPopGraphicsNotice();
  308.     #endif
  309.         GXDrawShape(invalidShape);
  310.         GXDisposeShape(invalidShape);
  311.     }
  312. #endif
  313. }
  314.  
  315. static void InvalidatePicture(shapeControlPtr targetPtr, gxShape control)
  316. {
  317.     /*** later, we need to fix the picture code below and then only invalidate the bounds of the sub-items */
  318.     if( false && GXGetShapeType(control) == gxPictureType ) {
  319.         long count = GXGetPicture(control, nil, nil, nil, nil);
  320.         while( count > 0 ) {
  321.             gxShape item;
  322.             gxTransform itemXform, originalXform;
  323.             gxRectangle bounds;
  324.  
  325.             GXGetPictureParts(control, count, 1, &item, nil, nil, &itemXform);
  326.             if( itemXform ) {
  327.                 originalXform = GXGetShapeTransform(item);
  328.                 GXSetShapeTransform(item, itemXform);
  329.             }
  330.             InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  331.             if( itemXform )
  332.                 GXSetShapeTransform(item, originalXform);
  333.             --count;
  334.         }
  335.     } else {
  336.         gxRectangle bounds;
  337.         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(control, &bounds));
  338.     }
  339. }
  340.  
  341. static void RedrawShapeControl(shapeControlPtr targetPtr)
  342. {
  343.     if( targetPtr->invalidAreas ) {
  344.         gxRectangle *listPtr, *maxPtr;
  345.  
  346.         /* lock down our handle of rectangular invalid areas */
  347.         HLock((Handle)targetPtr->invalidAreas);
  348.         listPtr = *targetPtr->invalidAreas;
  349.         maxPtr = (gxRectangle *)((char *)listPtr + GetHandleSize((Handle)targetPtr->invalidAreas));
  350.  
  351.         do {    gxShape clip;
  352.             gxViewPort *portList, *childList;
  353.             gxViewPort **childPorts;
  354.  
  355.             /* make a gxViewPort list for all of our child ports (these will hold our invalid clip) */
  356.             childPorts = (gxViewPort **)NewHandleClear( GetHandleSize((Handle)targetPtr->originalPorts) );
  357.             childList = DereferenceViewPortList(childPorts);
  358.  
  359.             /* clip all of the viewPorts that we are drawing into to our current invalid gxRectangle */
  360.             clip = GXNewRectangle(listPtr);
  361.             portList = DereferenceViewPortList(targetPtr->originalPorts);
  362.             while( *portList ) {
  363.                 *childList = GXNewViewPort(GXGetViewPortViewGroup(*portList));
  364.                 GXSetViewPortParent(*childList, *portList);
  365.                 GXSetViewPortClip(*childList, clip);
  366.                 ++portList;
  367.                 ++childList;
  368.             }
  369.             ReleaseViewPortList(targetPtr->originalPorts);
  370.             GXDisposeShape(clip);
  371.  
  372.             /* re-direct all of our drawing into the child viewPorts */
  373.         #ifdef debugging    
  374.             GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  375.         #endif
  376.             {    long portCount = CountViewPortList(childPorts);
  377.  
  378.                 childList = *childPorts;
  379.                 GXSetShapeViewPorts(targetPtr->background, portCount, childList);
  380.                 if( targetPtr->foreground )
  381.                     GXSetShapeViewPorts(targetPtr->foreground, portCount, childList);
  382.                 if( targetPtr->controls )
  383.                     GXSetShapeViewPorts(targetPtr->controls, portCount, childList);
  384.             }
  385.         #ifdef debugging    
  386.             GXPopGraphicsNotice();
  387.         #endif
  388.  
  389.             GXDrawShape(targetPtr->background);
  390.  
  391.             /* draw all the items in the picture, one at a time in the proper order using our clip */
  392.         #if 1
  393.             {    long count = GXGetPicture(targetPtr->items, nil, nil, nil, nil);
  394.                 long index;
  395.  
  396.                 for(index = 1; index <= count; ++index ) {
  397.                     gxViewPort **itemList;
  398.                     gxShape item;
  399.  
  400.                     GXGetPictureParts(targetPtr->items, index, 1, &item, nil, nil, nil);
  401.  
  402.                     /* save the item’s gxViewPort list and re-direct the item into our childPorts */
  403.                     itemList = NewViewPortListFromShape(item);
  404.                     GXSetShapeViewPorts(item, CountViewPortList(childPorts), *childPorts);
  405.  
  406.                     GXDrawShape(item);
  407.  
  408.                     /* restore the item’s gxViewPort list and throw away our saved list memory */
  409.                     DereferenceViewPortList(itemList);
  410.                     GXSetShapeViewPorts(item, CountViewPortList(itemList), *itemList);
  411.                     DisposeViewPortList(itemList);
  412.                 }
  413.             }
  414.         #else
  415.             GXDrawShape(targetPtr->items);
  416.         #endif
  417.  
  418.             if( targetPtr->foreground )
  419.                 GXDrawShape(targetPtr->foreground);
  420.             if( targetPtr->controls )
  421.                 GXDrawShape(targetPtr->controls);
  422.  
  423.             /* re-direct all of our drawing back into the main viewPorts */
  424.         #ifdef debugging
  425.             GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  426.         #endif
  427.             {
  428.                 long portCount = CountViewPortList(targetPtr->originalPorts);
  429.  
  430.                 portList = DereferenceViewPortList(targetPtr->originalPorts);
  431.                 GXSetShapeViewPorts(targetPtr->background, portCount, portList);
  432.                 if( targetPtr->foreground )
  433.                     GXSetShapeViewPorts(targetPtr->foreground, portCount, portList);
  434.                 if( targetPtr->controls )
  435.                     GXSetShapeViewPorts(targetPtr->controls, portCount, portList);
  436.                 ReleaseViewPortList(targetPtr->originalPorts);
  437.             }
  438.         #ifdef debugging
  439.             GXPopGraphicsNotice();
  440.         #endif
  441.             /* dispose of all the viewPorts in the child list and then dispose of the child list itself */
  442.             childList = DereferenceViewPortList(childPorts);
  443.             while( *childList )
  444.                 GXDisposeViewPort(*childList++);
  445.             ReleaseViewPortList(childPorts);
  446.             DisposeViewPortList(childPorts);
  447.         } while( ++listPtr < maxPtr );
  448.  
  449.         /* throw away all the invalid areas since we have just updated them */
  450.         DisposeHandle((Handle)targetPtr->invalidAreas);
  451.         targetPtr->invalidAreas = nil;
  452.     }
  453. }
  454.  
  455. static void BuildSelectionControls(shapeControlPtr targetPtr)
  456. {
  457.     gxRectangle bounds, controlBounds;
  458.     gxShape controls, workShape;
  459.  
  460.     /* don’t re-build anything if building is hidden */
  461.     if( targetPtr->buildHiddenLevel )
  462.         return;
  463.  
  464.     /* throw away the old controls and see if there is a selection; if not, then we are done */
  465.     DisposeShapeAt(&targetPtr->controls);
  466.     if( targetPtr->selection == nil )
  467.         return;
  468.  
  469.     /* create a new controls picture in all of our viewPorts */
  470.     controls = targetPtr->controls = GXNewShape(gxPictureType);
  471.     { gxViewPort *portList = DereferenceViewPortList(targetPtr->originalPorts);
  472.     GXSetShapeViewPorts(targetPtr->controls, CountViewPortList(targetPtr->originalPorts), portList);
  473.     ReleaseViewPortList(targetPtr->originalPorts); }
  474.  
  475.     /* get the bounds of the entire selection and calculate the controlBounds */
  476.     GetShapeTransformedBounds(targetPtr->selection, &bounds);
  477.     IfDebug(bounds.right < bounds.left || bounds.bottom < bounds.top, "\pthe selection should always have a bounds here");
  478.     controlBounds.left = bounds.left - controlHandleGap;
  479.     controlBounds.top = bounds.top - controlHandleGap;
  480.     controlBounds.right = bounds.right + controlHandleGap;
  481.     controlBounds.bottom = bounds.bottom + controlHandleGap;
  482.  
  483.     /* create the scale controls (these are simply rotated images of each other) */
  484.     {    static long scalePoly[] =    { 1, 4,
  485.                                 ff(0), ff(0),
  486.                                 ff(5) + 0x5000, ff(0),
  487.                                 ff(0), ff(5) + 0x5000,
  488.                                 ff(0), ff(0)
  489.                             };
  490.         gxShape tempShape;
  491.  
  492.         workShape = GXNewPolygons((gxPolygons *)scalePoly);
  493.         GXSetShapeFill(workShape, gxWindingFill);
  494.     #ifdef debugging
  495.         GXIgnoreGraphicsNotice(color_already_set);
  496.     #endif
  497.             SetShapeCommonColor(workShape, gxBlack);
  498.     #ifdef debugging
  499.         GXPopGraphicsNotice();
  500.     #endif
  501.         tempShape = GXCopyToShape(nil, workShape);
  502.         GXMoveShapeTo(tempShape, controlBounds.left, controlBounds.top);
  503.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  504.         GXDisposeShape(tempShape);
  505.  
  506.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  507.         tempShape = GXCopyToShape(nil, workShape);
  508.         GXMoveShapeTo(tempShape, controlBounds.right, controlBounds.top);
  509.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  510.         GXDisposeShape(tempShape);
  511.  
  512.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  513.         tempShape = GXCopyToShape(nil, workShape);
  514.         GXMoveShapeTo(tempShape, controlBounds.right, controlBounds.bottom);
  515.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  516.         GXDisposeShape(tempShape);
  517.  
  518.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  519.         tempShape = GXCopyToShape(nil, workShape);
  520.         GXMoveShapeTo(tempShape, controlBounds.left, controlBounds.bottom);
  521.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  522.         GXDisposeShape(tempShape);
  523.  
  524.         GXDisposeShape(workShape);
  525.     }
  526.  
  527.     /* create the skew controls in (top, right, bottom, left) order */
  528.     {    static long skewPoly[] =    { 1, 5,
  529.                                 -ff(3), ff(0),
  530.                                 ff(0), -ff(3),
  531.                                 ff(3), ff(0),
  532.                                 ff(0), ff(3),
  533.                                 -ff(3), ff(0)
  534.                             };
  535.         gxShape tempShape;
  536.         gxPoint center;
  537.  
  538.         center.x = (bounds.right >> 1) + (bounds.left >> 1);
  539.         center.y = (bounds.top >> 1) + (bounds.bottom >> 1);
  540.  
  541.         workShape = GXNewPolygons((gxPolygons *)skewPoly);
  542.         GXSetShapeFill(workShape, gxWindingFill);
  543.     #ifdef debugging
  544.         GXIgnoreGraphicsNotice(color_already_set);
  545.     #endif
  546.             SetShapeCommonColor(workShape, gxBlack);
  547.     #ifdef debugging
  548.         GXPopGraphicsNotice();
  549.     #endif
  550.  
  551.         tempShape = GXCopyToShape(nil, workShape);
  552.         GXMoveShape(tempShape, center.x, controlBounds.top);
  553.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  554.         GXDisposeShape(tempShape);
  555.  
  556.         tempShape = GXCopyToShape(nil, workShape);
  557.         GXMoveShape(tempShape, controlBounds.right + ff(1), center.y);
  558.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  559.         GXDisposeShape(tempShape);
  560.  
  561.         tempShape = GXCopyToShape(nil, workShape);
  562.         GXMoveShape(tempShape, center.x, controlBounds.bottom + ff(3));
  563.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  564.         GXDisposeShape(tempShape);
  565.  
  566.         tempShape = GXCopyToShape(nil, workShape);
  567.         GXMoveShape(tempShape, controlBounds.left, center.y);
  568.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  569.         GXDisposeShape(tempShape);
  570.  
  571.         GXDisposeShape(workShape);
  572.     }
  573.  
  574.     /* create the rotate control and its center gxPoint */
  575.     {    gxRectangle tempRect;
  576.         gxPoint center;
  577.  
  578.         /* get the center of the selection (or the individual gxShape) for the rotate control and its center gxPoint */
  579.         if( targetPtr->savedCenter.x != gxPositiveInfinity ) {
  580.             center = targetPtr->savedCenter;
  581.         } else if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 1 ) {
  582.             gxShape item;
  583.  
  584.             GXGetPicture(targetPtr->selection, &item, nil, nil, nil);
  585.             GetShapeTransformedCenter(item, ¢er);
  586.         } else {
  587.             center.x = (bounds.right >> 1) + (bounds.left >> 1);
  588.             center.y = (bounds.top >> 1) + (bounds.bottom >> 1);
  589.         }
  590.  
  591.         /* create the rotate control */
  592.         tempRect.left        = center.x - rotateControlRadius;
  593.         tempRect.top        = center.y - rotateControlRadius;
  594.         tempRect.right        = center.x + rotateControlRadius;
  595.         tempRect.bottom = center.y + rotateControlRadius;
  596.         workShape = NewOval(&tempRect);
  597.         GXSetShapeFill(workShape, gxClosedFrameFill);
  598.     #ifdef debugging
  599.         GXIgnoreGraphicsNotice(color_already_set);
  600.     #endif
  601.             SetShapeCommonColor(workShape, gxBlack);
  602.     #ifdef debugging
  603.         GXPopGraphicsNotice();
  604.     #endif
  605.     #if useFilledOval
  606.         GXSetShapePen(workShape, ff(2));
  607.         GXPrimitiveShape(workShape);
  608.         GXSetShapeFill(workShape, gxWindingFill);
  609.     #endif
  610.         GXSetPictureParts(controls, 1, 0, 1, &workShape, nil, nil, nil);
  611.         GXDisposeShape(workShape);
  612.  
  613.         /* create the rotate center */
  614.         tempRect.left        = center.x - rotateCenterSize;
  615.         tempRect.top        = center.y - rotateCenterSize;
  616.         tempRect.right        = center.x + rotateCenterSize;
  617.         tempRect.bottom = center.y + rotateCenterSize;
  618.         workShape = NewOval(&tempRect);
  619.         GXSetShapeFill(workShape, gxWindingFill);
  620.     #ifdef debugging
  621.         GXIgnoreGraphicsNotice(color_already_set);
  622.     #endif
  623.             SetShapeCommonColor(workShape, gxBlack);
  624.     #ifdef debugging
  625.         GXPopGraphicsNotice();
  626.     #endif
  627.         GXSetPictureParts(controls, 2, 0, 1, &workShape, nil, nil, nil);
  628.         GXDisposeShape(workShape);
  629.     }
  630.  
  631.     /* reset the savedCenter to the “no center saved” value */
  632.     targetPtr->savedCenter.x = targetPtr->savedCenter.y = gxPositiveInfinity;
  633. }
  634.  
  635. static void TrackShapeControl(shapeControlPtr targetPtr, long command, gxPoint originalClick)
  636. {
  637.     Boolean hiddenControls = false;
  638.     Boolean rebuildControls = false;
  639.     gxPolar originalPolar;
  640.     gxPoint originalSize;
  641.     gxPoint pivot;
  642.     gxPoint adjust;
  643.     gxMapping oldMatrix;
  644.     gxPoint oldMouse = originalClick;
  645.     Fixed polySrc[10];
  646.     Fixed polyDst[10];
  647.  
  648.     /* if we are rotating, then calculate the pivot gxPoint for the rotation and save the original angle */
  649.     if( command == commandRotate ) {
  650.         gxShape centerShape;
  651.         gxPoint tempPoint;
  652.  
  653.         GXGetPictureParts(targetPtr->controls, commandMoveRotateCenter, 1, ¢erShape, nil, nil, nil);
  654.         GetShapeTransformedCenter(centerShape, &pivot);
  655.         tempPoint.x = originalClick.x - pivot.x;
  656.         tempPoint.y = originalClick.y - pivot.y;
  657.         PointToPolar(&tempPoint, &originalPolar);
  658.         targetPtr->savedCenter = pivot;
  659.     } else if( command >= commandScale && command <= commandSkew + 3 ) {
  660.         gxRectangle bounds;
  661.  
  662.         /* get the bounds of the entire selection for all the commands that require it */
  663.         GetShapeTransformedBounds(targetPtr->selection, &bounds);
  664.         IfDebug(bounds.right < bounds.left || bounds.bottom < bounds.top, "\pthe selection should always have a bounds here");
  665.  
  666.         /* calculate the originalSize of the selection and set the original scale gxMapping to identity */
  667.         originalSize.x = bounds.right - bounds.left;
  668.         originalSize.y = bounds.bottom - bounds.top;
  669.         ResetMapping(&oldMatrix);
  670.  
  671.         /* calculate the pivot (or anchor) gxPoint for the scale */
  672.         switch( command ) {
  673.             case commandScale + leftTop:
  674.                 pivot.x = bounds.right;
  675.                 pivot.y = bounds.bottom;
  676.                 break;
  677.             case commandScale + rightTop:
  678.                 pivot.x = bounds.left;
  679.                 pivot.y = bounds.bottom;
  680.                 break;
  681.             case commandScale + rightBottom:
  682.                 pivot.x = bounds.left;
  683.                 pivot.y = bounds.top;
  684.                 break;
  685.             case commandScale + leftBottom:
  686.                 pivot.x = bounds.right;
  687.                 pivot.y = bounds.top;
  688.                 break;
  689.             default:
  690.                 polySrc[0] = 4;
  691.                 polySrc[1] = bounds.left;
  692.                 polySrc[2] = bounds.top;
  693.                 polySrc[3] = bounds.right;
  694.                 polySrc[4] = bounds.top;
  695.                 polySrc[5] = bounds.right;
  696.                 polySrc[6] = bounds.bottom;
  697.                 polySrc[7] = bounds.left;
  698.                 polySrc[8] = bounds.bottom;
  699.                 BlockMove(&polySrc[0], &polyDst[0], sizeof(polySrc));
  700.         }
  701.  
  702.         /* this is the amount that we must adjust the mouse position by in order to have the scaling come out correct */
  703.         adjust.x = FixedAbs(originalClick.x - pivot.x) - originalSize.x;
  704.         adjust.y = FixedAbs(originalClick.y - pivot.y) - originalSize.y;
  705.     }
  706.  
  707.     while( StillDown() ) {
  708.         gxPoint newMouse;
  709.  
  710.         GXGetViewPortMouse(0, &newMouse);
  711.         if( newMouse.x != oldMouse.x || newMouse.y != oldMouse.y ) {
  712.             Fixed deltaX = newMouse.x - oldMouse.x;
  713.             Fixed deltaY = newMouse.y - oldMouse.y;
  714.             gxMapping newMatrix;
  715.             gxRectangle bounds;
  716.             long count;
  717.             gxShape item;
  718.  
  719.             /* if the controls are not hidden yet, then do so now, unless we are moving the rotate center */
  720.             if( command != commandMoveRotateCenter && ! hiddenControls ) {
  721.                 /* set our flag so that we won’t try to hide the controls again */
  722.                 hiddenControls = true;
  723.  
  724.                 /* mark the area covered by the controls as invalid and then temporarily hide the controls picture */
  725.                 InvalidatePicture(targetPtr, targetPtr->controls);
  726.                 GXSetShapeFill(targetPtr->controls, gxNoFill);
  727.                 RedrawShapeControl(targetPtr);
  728.             }
  729.  
  730.             switch( command ) {
  731.  
  732.                 case commandSelect:
  733.                     /*** needs work */
  734.                     break;
  735.  
  736.                 case commandMove:
  737.                     /* move all the items in the selection, marking their old and new bounds as redraw areas */
  738.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  739.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  740.                     do {
  741.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  742.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  743.                         GXMoveShape(item, deltaX,  deltaY);
  744.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  745.                     } while( --count );
  746.  
  747.                     /* move the controls for all the selected items and update the screen to reflect the new position */
  748.                     GXMoveShape(targetPtr->controls, deltaX, deltaY);
  749.                     RedrawShapeControl(targetPtr);
  750.                     break;
  751.  
  752.                 case commandRotate:
  753.                 {    gxPoint tempPoint;
  754.                     gxPolar newPolar;
  755.  
  756.                     tempPoint.x = newMouse.x - pivot.x;
  757.                     tempPoint.y = newMouse.y - pivot.y;
  758.                     PointToPolar(&tempPoint, &newPolar);
  759.  
  760.                     /* rotate all the items in the selection, marking their old and new bounds as redraw areas */
  761.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  762.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  763.                     do {
  764.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  765.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  766.                         GXRotateShape(item, newPolar.angle - originalPolar.angle, pivot.x, pivot.y);
  767.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  768.                     } while( --count );
  769.  
  770.                     /* copy the newPolar into the originalPolar for our next pass through the loop */
  771.                     originalPolar = newPolar;
  772.  
  773.                     /* invalidate the controls for all the selected items and update the screen to reflect the new position */
  774.                     rebuildControls = true;
  775.                     RedrawShapeControl(targetPtr);
  776.                     break;
  777.                 }
  778.  
  779.                 case commandMoveRotateCenter:
  780.                 {    long index;
  781.  
  782.                     /* move the rotate center control part and the rotate control part */
  783.                     for(index = commandRotate; index <= commandMoveRotateCenter; ++index) {
  784.                         gxShape control;
  785.                         gxTransform xform;
  786.                         long pass;
  787.  
  788.                         GXGetPictureParts(targetPtr->controls, index, 1, &control, nil, nil, &xform);
  789.                         /* invalidate the position before the move and the position after the move */
  790.                         for( pass = 0; pass <= 1; ++pass ) {
  791.                             gxShape boundsShape;
  792.                             gxTransform originalXform;
  793.                             gxMapping matrix;
  794.  
  795.                             if( xform ) {
  796.                                 originalXform = GXGetShapeTransform(control);
  797.                                 GXSetShapeTransform(control, xform);
  798.                             }
  799.                             boundsShape = GXNewRectangle(GetShapeTransformedBounds(control, &bounds));
  800.                             if( xform )
  801.                                 GXSetShapeTransform(control, originalXform);
  802.                             GXMapShape(boundsShape, GXGetShapeMapping(targetPtr->controls, &matrix));
  803.                             GXGetShapeBounds(boundsShape, 0L, &bounds);
  804.                             GXDisposeShape(boundsShape);
  805.                             InvalidateRectangle(targetPtr, &bounds);
  806.  
  807.                             /* if we are on our first pass, then move the control */
  808.                             if( pass == 0 )
  809.                                 GXMoveShape(control, deltaX, deltaY);
  810.                         }
  811.                     }
  812.  
  813.                     /* update the screen to reflect the new positions of these controls */
  814.                     RedrawShapeControl(targetPtr);
  815.                     break;
  816.                 }
  817.  
  818.                 /* scale the gxShape around the pivot gxPoint */
  819.                 case commandScale + leftTop:
  820.                 case commandScale + rightTop:
  821.                 case commandScale + rightBottom:
  822.                 case commandScale + leftBottom:
  823.                 {    Fixed scaleX, scaleY;
  824.  
  825.                     /* calculate the scale values for the selection */
  826.                     scaleX = FixedDivide(FixedAbs(newMouse.x - pivot.x) - adjust.x, originalSize.x);
  827.                     scaleY = FixedDivide(FixedAbs(newMouse.y - pivot.y) - adjust.y, originalSize.y);
  828.  
  829.                     /* if we are on the other side of the pivot gxPoint from where we started, then negate the scale */
  830.                     if( ((newMouse.x - pivot.x) ^ (originalClick.x - pivot.x)) < 0 )
  831.                         scaleX = -scaleX;
  832.                     if( ((newMouse.y - pivot.y) ^ (originalClick.y - pivot.y)) < 0 )
  833.                         scaleY = -scaleY;
  834.  
  835.                     /* save our new true scale gxMapping and calculate the delta gxMapping to apply to all the shapes */
  836.                     ResetMapping(&newMatrix);
  837.                     ScaleMapping(&newMatrix, scaleX, scaleY, pivot.x, pivot.y);
  838.                     goto mapSelection;
  839.                 }
  840.  
  841.                 case commandSkew + centerTop:
  842.                     polyDst[1] += deltaX;
  843.                     polyDst[2] += deltaY;
  844.                     polyDst[3] = polyDst[1] + originalSize.x;
  845.                     polyDst[4] = polyDst[2];
  846.                     goto skewSelection;
  847.  
  848.                 case commandSkew + centerRight:
  849.                     polyDst[3] += deltaX;
  850.                     polyDst[4] += deltaY;
  851.                     polyDst[5] = polyDst[3];
  852.                     polyDst[6] = polyDst[4] + originalSize.y;
  853.                     goto skewSelection;
  854.  
  855.                 case commandSkew + centerBottom:
  856.                     polyDst[7] += deltaX;
  857.                     polyDst[8] += deltaY;
  858.                     polyDst[5] = polyDst[7] + originalSize.x;
  859.                     polyDst[6] = polyDst[8];
  860.                     goto skewSelection;
  861.  
  862.                 case commandSkew + centerLeft:
  863.                     polyDst[1] += deltaX;
  864.                     polyDst[2] += deltaY;
  865.                     polyDst[7] = polyDst[1];
  866.                     polyDst[8] = polyDst[2] + originalSize.y;
  867.                     /* fall through into “skewSelection” */
  868.  
  869. skewSelection:            PolyToPolyMap((gxPolygon *)&polySrc, (gxPolygon *)&polyDst, &newMatrix);
  870.                     /* fall through into “mapSelection” */
  871.  
  872. mapSelection:        {    gxMapping deltaMatrix, tempMatrix;
  873.  
  874.                     /* calculate the delta gxMapping */
  875.                     CopyToMapping(&tempMatrix, &newMatrix);
  876.                     InvertMapping(&deltaMatrix, &oldMatrix);
  877.                     MapMapping(&deltaMatrix, &tempMatrix);
  878.  
  879.                     /* map all the shapes in the selection (and invalidate them) */
  880.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  881.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  882.                     do {    gxShape itemCopy;
  883.     
  884.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  885.                         itemCopy = GXCopyToShape(nil, item);
  886.                         GXMapShape(itemCopy, &deltaMatrix);
  887.                         GetShapeTransformedBounds(itemCopy, &bounds);
  888.                         GXDisposeShape(itemCopy);
  889.                         if( bounds.right >= bounds.left + minimumObjectSizeH &&
  890.                             bounds.bottom >= bounds.top + minimumObjectSizeV ) {
  891.                                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  892.                                 GXMapShape(item, &deltaMatrix);
  893.                                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  894.                         } else {
  895.                             /* if the object would have disappeared, then keep the objects old full-delta gxMapping */
  896.                             CopyToMapping(&newMatrix, &oldMatrix);
  897.                         }
  898.                     } while( --count );
  899.  
  900.                     /* save the new full-delta gxMapping in our original gxMapping variable */
  901.                     CopyToMapping(&oldMatrix, &newMatrix);
  902.  
  903.                     /* update the screen to reflect changes to the gxShape and invalidate the controls */
  904.                     RedrawShapeControl(targetPtr);
  905.                     rebuildControls = true;
  906.                     break;
  907.                 }
  908.             }
  909.  
  910.             oldMouse = newMouse;
  911.         }
  912.     }
  913.  
  914.     /* if we changed the geometry of the selection, then re-build the controls for it */
  915.     if( rebuildControls )
  916.         BuildSelectionControls(targetPtr);
  917.  
  918.     if( hiddenControls ) {
  919.         GXSetShapeFill(targetPtr->controls, gxEvenOddFill);
  920.         GXDrawShape(targetPtr->controls);
  921.     }
  922. }
  923.  
  924. static void DeselectShape(shapeControlPtr targetPtr, gxShape item)
  925. {
  926.     if( targetPtr->selection ) {
  927.         long count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  928.         gxShape temp;
  929.  
  930.         do {
  931.             GXGetPictureParts(targetPtr->selection, count, 1, &temp, nil, nil, nil);
  932.         } while( temp != item && --count );
  933.  
  934.         /* if we actually found the item in the selection, then de-select it */
  935.         if( count ) {
  936.             /* invalidate the controls for the selection, since we are changing it */
  937.             InvalidatePicture(targetPtr, targetPtr->controls);
  938.  
  939.             /* remove the item from the selection picture and dispose of the picture item was the only selected gxShape */
  940.             GXSetPictureParts(targetPtr->selection, count, 1, 0, nil, nil, nil, nil);
  941.             if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 0 )
  942.                 DisposeShapeAt(&targetPtr->selection);
  943.  
  944.             /* rebuild the controls for the selection */
  945.             BuildSelectionControls(targetPtr);
  946.         }
  947.     }
  948. }
  949.  
  950. static void SelectShape(shapeControlPtr targetPtr, gxShape item, Boolean replaceSelection, Boolean bringToFront)
  951. {
  952.     /* move the passed item gxShape to the front of the picture if we are supposed to do so */
  953.     if( bringToFront ) {
  954.         long index = GXGetPicture(targetPtr->items, nil, nil, nil, nil);
  955.         gxShape tempShape;
  956.  
  957.         IfDebug(index == 0, "\ptargetPtr->items must always have items");
  958.         do {
  959.             GXGetPictureParts(targetPtr->items, index, 1, &tempShape, nil, nil, nil);
  960.             if( tempShape == item ) {
  961.                 gxRectangle bounds;
  962.  
  963.                 /* add the gxShape to the end first, then remove it so that our references are always valid */
  964.                 GXSetPictureParts(targetPtr->items, 0, 0, 1, &item, nil, nil, nil);
  965.                 GXSetPictureParts(targetPtr->items, index, 1, 0, nil, nil, nil, nil);
  966.                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  967.             }
  968.         } while( --index );
  969.     }
  970.  
  971.     /* if we are replacing the selection, then un-select all the selected shapes */
  972.     if( replaceSelection && targetPtr->selection ) {
  973.         InvalidatePicture(targetPtr, targetPtr->controls);
  974.         DisposeShapeAt(&targetPtr->selection);
  975.         DisposeShapeAt(&targetPtr->controls);
  976.     }
  977.  
  978.     /* if there is an old selection that we are adding to, then invalidate the area for its controls */
  979.     if( targetPtr->controls )
  980.         InvalidatePicture(targetPtr, targetPtr->controls);
  981.  
  982.     if( item == nil ) {
  983.         DisposeShapeAt(&targetPtr->controls);
  984.         return;
  985.     }
  986.  
  987.     if( targetPtr->selection == nil ) {
  988.         targetPtr->selection = GXNewPicture(1, &item, nil, nil, nil);
  989.     } else {
  990.         long count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  991.         long index = 0;
  992.         gxShape selectionItem;
  993.         gxShape mainItem;
  994.  
  995.         /* look through the selection for the first gxShape AFTER item in the main items picture; insert item before this index */
  996.         IfDebug(count == 0, "\pselection must have items in it here");
  997.         do {    long mainIndex = 0;
  998.  
  999.             GXGetPictureParts(targetPtr->selection, ++index, 1, &selectionItem, nil, nil, nil);
  1000.             do {
  1001.                 IfDebug(mainIndex == GXGetPicture(targetPtr->items, nil, nil, nil, nil),
  1002.                     "\pselected item not in main items picture");
  1003.                 GXGetPictureParts(targetPtr->items, ++mainIndex, 1, &mainItem, nil, nil, nil);
  1004.             } while( mainItem != selectionItem && mainItem != item );
  1005.         } while( mainItem == selectionItem && --count );
  1006.  
  1007.         /* if we hit the end of the selection without finding a gxShape after item, then set index to zero to insert at the end */
  1008.         if( count == 0 )    index = 0;
  1009.  
  1010.         /* insert our newly selected item in the correct place in the selection */
  1011.         GXSetPictureParts(targetPtr->selection, index, 0, 1, &item, nil, nil, nil);
  1012.     }
  1013.  
  1014.     /* calculate the geometry of the controls for the selection and invalidate this new area */
  1015.     BuildSelectionControls(targetPtr);
  1016.     InvalidatePicture(targetPtr, targetPtr->controls);
  1017. }
  1018.  
  1019.  
  1020. /*----------------------------------------------------------------------------------------------------*/
  1021. /* public routines */
  1022. /*----------------------------------------------------------------------------------------------------*/
  1023.  
  1024.  
  1025. shapeControl NewShapeControl(gxShape items, gxShape background, gxShape foreground)
  1026. {
  1027.     shapeControl target;
  1028.  
  1029.     NilShapeReturnNil(items);
  1030.     IfWarningReturnNil(GXGetShapeType(items) != gxPictureType, picture_expected);
  1031.     IfDebug(firstIndexCommand <= lastSpecialCommand, "\pinvalid gxShape control commands - bug in gxShape control library");
  1032.  
  1033.     if( (target = (shapeControl)NewHandleClear(sizeof(shapeControlRecord))) != 0 ) {
  1034.         gxViewPort **originalPorts = NewViewPortListFromShape(items);
  1035.         shapeControlPtr targetPtr;
  1036.  
  1037.         HLock((Handle)target);
  1038.         targetPtr = *target;
  1039.  
  1040.         targetPtr->items = GXCloneShape(items);
  1041.  
  1042.         if( background )
  1043.             targetPtr->background = GXCloneShape(background);
  1044.         else {
  1045.             gxColor whiteColor;
  1046.  
  1047.             /* if there is no background, then create a white full gxShape (we always need some sort of background) */
  1048.             targetPtr->background = GXNewShape(gxFullType);
  1049.             whiteColor.space = gxRGBSpace;
  1050.             whiteColor.profile = nil;
  1051.             whiteColor.element.rgb.red = 0xFFFF;
  1052.             whiteColor.element.rgb.green = 0xFFFF;
  1053.             whiteColor.element.rgb.blue = 0xFFFF;
  1054.             GXSetShapeColor(targetPtr->background, &whiteColor);
  1055.         }
  1056.         /* ensure that the background draws to the same ports as the items */
  1057.         DereferenceViewPortList(originalPorts);
  1058.     #ifdef debugging
  1059.         GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  1060.     #endif
  1061.             GXSetShapeViewPorts(targetPtr->background, CountViewPortList(originalPorts), *originalPorts);
  1062.     #ifdef debugging
  1063.         GXPopGraphicsNotice();
  1064.     #endif
  1065.         if( foreground ) {
  1066.             targetPtr->foreground = GXCloneShape(foreground);
  1067.             GXSetShapeViewPorts(targetPtr->foreground, CountViewPortList(originalPorts), *originalPorts);
  1068.         }
  1069.  
  1070.         /* set the savedCenter to the “no center saved” value */
  1071.         targetPtr->savedCenter.x = targetPtr->savedCenter.y = gxPositiveInfinity;
  1072.  
  1073.         ReleaseViewPortList(originalPorts);
  1074.         targetPtr->originalPorts = originalPorts;
  1075.         HUnlock((Handle)target);
  1076.     }
  1077.  
  1078.     return target;
  1079. }
  1080.  
  1081.  
  1082. void DisposeShapeControl(shapeControl target)
  1083. {
  1084.     shapeControlPtr targetPtr;
  1085.  
  1086.     NilParamReturn(target);
  1087.  
  1088.     HLock((Handle)target);
  1089.     targetPtr = *target;
  1090.     GXDisposeShape(targetPtr->items);
  1091.     GXDisposeShape(targetPtr->background);
  1092.     DisposeShapeAt(&targetPtr->foreground);
  1093.     DisposeShapeAt(&targetPtr->selection);
  1094.     DisposeShapeAt(&targetPtr->controls);
  1095.     DisposeViewPortList(targetPtr->originalPorts);
  1096.     HUnlock((Handle)target);
  1097.  
  1098.     /* dispose of the gxShape control memory itself */
  1099.     DisposeHandle((Handle)target);
  1100. }
  1101.  
  1102.  
  1103. gxShape GetShapeControlSelection(const shapeControl source)
  1104. {
  1105.     NilParamReturn(source);
  1106.  
  1107.     if( (*source)->selection )
  1108.         return GXCopyToShape(nil, (*source)->selection);
  1109.     else
  1110.         return nil;
  1111. }
  1112.  
  1113.  
  1114. void SetShapeControlsSelection(shapeControl target, gxShape shapesToSelect, Boolean bringToFront, Boolean replaceSelection)
  1115. {
  1116.     shapeControlPtr targetPtr;
  1117.     long count;
  1118.     Boolean updateControls = false;
  1119.  
  1120.     NilParamReturn(target);
  1121.     HLock((Handle)target);
  1122.     targetPtr = *target;
  1123.  
  1124.     /* prevent the selection’s controls from getting re-build by the various intermediate steps */
  1125.     ++targetPtr->buildHiddenLevel;
  1126.  
  1127.     /* if we are replacing the old selection, then de-select all the old shapes that will not be in the new selection */
  1128.     if( replaceSelection && targetPtr->selection ) {
  1129.         count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  1130.  
  1131.         do {    /* scan through all the items in the selection, de-selecting all the ones not in shapesToSelect */
  1132.             gxShape item;
  1133.             Boolean itemInNewSelection;
  1134.  
  1135.             GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  1136.             itemInNewSelection = false;
  1137.             if( shapesToSelect == nil || (shapesToSelect != item && GXGetShapeType(shapesToSelect) == gxPictureType
  1138.                 && ShapeInPicture(item, shapesToSelect) == false) ) {
  1139.                     updateControls = true;
  1140.                     GXSetPictureParts(targetPtr->selection, count, 1, 0, nil, nil, nil, nil);
  1141.             }
  1142.         } while( --count );
  1143.  
  1144.         if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 0 ) {
  1145.             DisposeShapeAt(&targetPtr->selection);
  1146.             DisposeShapeAt(&targetPtr->controls);
  1147.         }
  1148.     }
  1149.  
  1150.     /* if there is a new selection, then remove any already selected shapes from it */
  1151.     if( shapesToSelect ) {
  1152.         /* is the new selection a non-empty list of items? */
  1153.         if( GXGetShapeType(shapesToSelect) == gxPictureType && (count = GXGetPicture(shapesToSelect, nil, nil, nil, nil)) != 0) {
  1154.             /*** bring all the new items to the front in the order that they are in shapesToSelect */
  1155.  
  1156.             /* make a copy of the new item list to be selected so that we can munge the list */
  1157.             shapesToSelect = GXCopyToShape(nil, shapesToSelect);
  1158.  
  1159.             do {    /* remove all the already selected items from our new list to select */
  1160.                 gxShape item;
  1161.  
  1162.                 /* if the item in our new selection is already selected, then remove it from our new selection */
  1163.                 GXGetPictureParts(shapesToSelect, count, 1, &item, nil, nil, nil);
  1164.                 if( targetPtr->selection && ShapeInPicture(item, targetPtr->selection) ||
  1165.                     ShapeInPicture(item, targetPtr->items) == false )
  1166.                         GXSetPictureParts(shapesToSelect, count, 1, 0, nil, nil, nil, nil);
  1167.             } while( --count );
  1168.  
  1169.             /* if there are any items left in the new list to be selected, then add them to the current selection */
  1170.             count = GXGetPicture(shapesToSelect, nil, nil, nil, nil);
  1171.             if (count) {
  1172.                 do {    /* note that we don’t bring these items to the front, since we have already done this */
  1173.                     gxShape item;
  1174.  
  1175.                     GXGetPictureParts(shapesToSelect, count, 1, &item, nil, nil, nil);
  1176.                     SelectShape(targetPtr, item, false, false);
  1177.                     updateControls = true;
  1178.                 } while( --count );
  1179.             }
  1180.  
  1181.             /* dispose of our munged copy of the new items that have been selected */
  1182.             GXDisposeShape(shapesToSelect);
  1183.         } else if( ShapeInPicture(shapesToSelect, targetPtr->selection) == false ) {
  1184.             if( ShapeInPicture(shapesToSelect, targetPtr->items) ) {
  1185.                 SelectShape(targetPtr, shapesToSelect, bringToFront, false);
  1186.                 updateControls = true;
  1187.             }
  1188.         }
  1189.     }
  1190.  
  1191.     /* allow the selection’s controls to get re-built and re-build them if they have changed */
  1192.     --targetPtr->buildHiddenLevel;
  1193.     if( updateControls )
  1194.         BuildSelectionControls(targetPtr);
  1195.  
  1196.     HUnlock((Handle)target);
  1197. }
  1198.  
  1199.  
  1200. gxShape GetShapeControlSelectionHandles(const shapeControl source)
  1201. {
  1202.     NilParamReturn(source);
  1203.  
  1204.     if( (*source)->controls )
  1205.         return GXCopyDeepToShape(nil, (*source)->controls);
  1206.     else
  1207.         return nil;
  1208. }
  1209.  
  1210.  
  1211. Boolean SendEventToShapeControl(shapeControl target, EventRecord *event)
  1212. {
  1213.     Boolean handledEvent = false;
  1214.  
  1215.     NilParamReturn(target);
  1216.     NilParamReturn(event);
  1217.  
  1218.     HLock((Handle)target);
  1219.     switch( event->what ) {
  1220.         case keyDown:
  1221.             break;    /*** change this to handle the key and return true if it is ours */
  1222.         case mouseDown:
  1223.         {    gxPoint originalClick;
  1224.             gxHitTestInfo result;
  1225.             gxShape itemShape;
  1226.             long command;
  1227.             shapeControlPtr targetPtr = *target;
  1228.  
  1229.             {    gxViewPort originalPort = *DereferenceViewPortList(targetPtr->originalPorts);
  1230.                 GXConvertQDPoint(&event->where, originalPort, &originalClick);
  1231.             }
  1232.  
  1233.             /* if there is a selection, then see if any of the selection’s controls have been hit and track them if they have */
  1234.             if( targetPtr->selection ) {
  1235.                 if( GXHitTestPicture(targetPtr->controls, &originalClick, &result, 1, 1) != nil/*gxNoPart*/ ) {
  1236.                     TrackShapeControl(targetPtr, result.containerIndex, originalClick);
  1237.                     handledEvent = true;
  1238.                     break;
  1239.                 }
  1240.             }
  1241.  
  1242.             /* see if an item gxShape was hit; if so then add it to the selection and track it */
  1243.             if( GXHitTestPicture(targetPtr->items, &originalClick, &result, 1, 1) != nil/*gxNoPart*/ ) {
  1244.                 command = commandMove;
  1245.                 itemShape = result.which;
  1246.  
  1247.                 if( targetPtr->selection && ShapeInPicture(itemShape, targetPtr->selection) ) {
  1248.                     if( event->modifiers & shiftKey ) {
  1249.                         /* the gxShape was already selected and we shift-clicked it, so deselect it and return */
  1250.                         DeselectShape(targetPtr, itemShape);
  1251.                         RedrawShapeControl(targetPtr);
  1252.                         handledEvent = true;
  1253.                         break;
  1254.                     }
  1255.                 } else {
  1256.                     /* select the new gxShape - the last two parameters are (Boolean replaceSelection, bringToFront) */
  1257.                     SelectShape(targetPtr, itemShape, (event->modifiers & shiftKey) == 0,
  1258.                         (event->modifiers & optionKey) != 0);
  1259.                     RedrawShapeControl(targetPtr);
  1260.                 }
  1261.  
  1262.                 /* allow the user to move the entire selection around */
  1263.                 TrackShapeControl(targetPtr, commandMove, originalClick);
  1264.                 handledEvent = true;
  1265.                 break;
  1266.             }
  1267.  
  1268.             /* see if we are within the bounds of any of the original viewPorts of the items */
  1269.             {    gxShape testShape = GXNewPoint(&originalClick);
  1270.                 gxViewPort *portList = DereferenceViewPortList(targetPtr->originalPorts);
  1271.  
  1272.             #ifdef debugging
  1273.                 GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  1274.             #endif
  1275.                     GXSetShapeViewPorts(testShape, CountViewPortList(targetPtr->originalPorts), portList);
  1276.             #ifdef debugging
  1277.                 GXPopGraphicsNotice();
  1278.             #endif
  1279.                 /* scan through all the original viewPorts of the items */
  1280.                 while( *portList ) {
  1281.                     /* if a gxPoint at the original click location would draw to any devices in this port, then we are
  1282.                     within the bounds of the port. we should now drag out a gray gxRectangle to select all the items
  1283.                     contained within it */
  1284.                     if( GXGetShapeGlobalViewDevices(testShape, *portList, nil) ) {
  1285.                         GXDisposeShape(testShape);
  1286.  
  1287.                     #if 0     /*** needs work */
  1288.                         TrackShapeControl(targetPtr, commandSelect, originalClick);
  1289.                     #else
  1290.                         /* for now, just de-select all the items */
  1291.                         SelectShape(targetPtr, nil, true, false);
  1292.                     #endif
  1293.  
  1294.                         RedrawShapeControl(targetPtr);
  1295.                         ReleaseViewPortList(targetPtr->originalPorts);
  1296.                         HUnlock((Handle)target);
  1297.                         return true;
  1298.                     }
  1299.                     ++portList;
  1300.                 }
  1301.                 GXDisposeShape(testShape);
  1302.                 ReleaseViewPortList(targetPtr->originalPorts);
  1303.             }
  1304.  
  1305.             /* nothing was hit, so break and return that we didn’t handle the event */
  1306.             break;
  1307.         }
  1308.     }
  1309.  
  1310.     HUnlock((Handle)target);
  1311.     return handledEvent;
  1312. }
  1313.  
  1314.  
  1315. void InvalidateShapeControlShape(shapeControl target, gxShape invalidShape)
  1316. {
  1317.     NilParamReturn(target);
  1318.     NilShapeReturn(invalidShape);
  1319.  
  1320.     HLock((Handle)target);
  1321.     InvalidatePicture(*target, invalidShape);
  1322.     HUnlock((Handle)target);
  1323. }
  1324.  
  1325.  
  1326. void InvalidateShapeControlRectangle(shapeControl target, gxRectangle *bounds)
  1327. {
  1328.     NilParamReturn(target);
  1329.     NilParamReturn(bounds);
  1330.  
  1331.     HLock((Handle)target);
  1332.     InvalidateRectangle(*target, bounds);
  1333.     HUnlock((Handle)target);
  1334. }
  1335.  
  1336.  
  1337. void UpdateShapeControl(shapeControl target)
  1338. {
  1339.     NilParamReturn(target);
  1340.     HLock((Handle)target);
  1341.     RedrawShapeControl(*target);
  1342.     HUnlock((Handle)target);
  1343. }
  1344.